﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RWLibrary.Parser;
using RWLibrary.Logic;
using System.Text.RegularExpressions;
using RWLibrary.Logic.Statements;

//TODO:
//sprawdzenie poprawnosci poprzez 'observable' i 'allways'
//czy napewno dobrze rozumiem 'releases'
namespace RWLibrary.Logic.Graph
{
    public class Graph
    {
        World _world = new World();
        List<Node> _nodes = new List<Node>();

        /// <summary>
        /// Lista wszystkich elementów grafu
        /// </summary>
        public List<Node> Nodes
        {
            get
            {
                return _nodes;
            }
        }

        public World World
        {
            get
            {
                return _world;
            }
        }
        /// <summary>
        /// Graf zbudowany z inputu
        /// </summary>
        /// <param name="input">Caly kod opisu swiata</param>
        /// 
        public Graph(string input) : this(Regex.Split(input, @"\r?\n|\r"))
        {
        }

        public Graph(string[] input)
        {

            StatementParser parser = new StatementParser( _world );
            input = input.Where(s => !string.IsNullOrEmpty(s)).ToArray();
            parser.ParseStatements(input);
            int c = 1 << _world.FluentCount;
            //tworzymy wszystkie stany ktore moga wystapic w grafie

            List<State> allStates = new List<State>();
            for (int i = 0; i < c; i++)
            {
                State s = _world.CreateState(i);
                if (
                    (_world.Statements.Constraint.Count==0 || _world.Statements.Constraint.All(x => x.Alpha.Test(s)) ) //zapewnienie constraintsów
                    /*&&
                    (
                        (_world.Statements.Initially != null && _world.Statements.Initially.Alpha.Test(s) ) ||
                        _world.Statements.Causes.Any( x => x.Pi.Test(s) || x.Alpha.Test(s) ) ||
                        _world.Statements.Release.Any( x => x.Pi.Test(s) )
                    )*/
                )
                {
                    allStates.Add(s);
                }
            }
            //Nody ktore moga byc initial. Aby byc initial musi spelniac warunek w initially oraz warunki z observable i allways
            List<Node> initialCandidates = new List<Node>();

            if (allStates.Count != 0)
            {
                List<State> notUsedStates = allStates.ToList();
                //budujemy graf
                Queue<Node> queue = new Queue<Node>();
                //nody dla initial statetów
                if (_world.Statements.Initially != null)
                {
                    foreach (State s in allStates)
                    {
                        if (_world.Statements.Initially.Alpha.Test(s))
                        {
                            Node n = new Node(s, false);
                            initialCandidates.Add(n);
                            notUsedStates.Remove(s);
                            queue.Enqueue(n);
                            _nodes.Add(n);
                        }
                    }
                }

                //generatory wszystkich akcji
                Dictionary<string, EdgeGenerator> generators = new Dictionary<string, EdgeGenerator>();
                foreach (CausesStatement cs in _world.Statements.Causes)
                {
                    if (!generators.ContainsKey(cs.ActionFamily.Name))
                    {
                        generators.Add(cs.ActionFamily.Name, new EdgeGenerator(cs.ActionFamily.Name, _world.Statements, allStates));
                    }
                }
                //czy napewno potrzebne?
                foreach (ReleaseStatement rs in _world.Statements.Release)
                {
                    if (!generators.ContainsKey(rs.ActionFamily.Name))
                    {
                        generators.Add(rs.ActionFamily.Name, new EdgeGenerator(rs.ActionFamily.Name, _world.Statements, allStates));
                    }
                }
                
                while (notUsedStates.Count > 0 || queue.Count > 0) //póki jakies stany jeszcze do grafu nie trafily
                {
                    if (queue.Count == 0) //albo nie ma initially albo zesmy te co od initially odchodza juz dolaczyli do grafu
                    {
                        State s = notUsedStates.First();
                        Node n = new Node(s, false);
                        queue.Enqueue(n);
                        notUsedStates.Remove(s);
                        Nodes.Add(n);
                    }
                    while (queue.Count > 0)
                    {
                        Node n = queue.Dequeue();
                        //dla kazdej akcji bieze jej generator i generuje wszystkie mozliwe krawedzie z danego stanu
                        foreach (EdgeGenerator eg in generators.Values)
                        {
                            List<KeyValuePair<Edge, State>> destinations = eg.getDestinations(n.State);
                            foreach (KeyValuePair<Edge, State> dest in destinations)
                            {
                                Node destNode = Nodes.Find(x => x.State.Equals(dest.Value));
                                if (destNode == null) //nie ma tego wierzcholka jeszcze w grafie
                                {
                                    destNode = new Node(dest.Value, false);
                                    notUsedStates.Remove(dest.Value);
                                    Nodes.Add(destNode);
                                    queue.Enqueue(destNode);
                                }
                                n.addConnection(new Edge(dest.Key.Name, dest.Key.Cost, destNode.State), destNode);
                            }
                        }
                    }
                }

                //Nie bylo zdania Initial - kazdy node moze byc initial
                if (initialCandidates.Count == 0)
                {
                    if (_world.Statements.Initially != null)
                    {
                        throw new ArgumentException("The initially statements are imposible!");
                    }
                    initialCandidates.AddRange(this._nodes);
                }
                List<Node> initialCandidatesCopy = new List<Node>(initialCandidates);
                //czy Observable jest prawidlowe
                foreach (ObservationStatement os in _world.Statements.Observation)
                {
                    foreach (Node n in initialCandidatesCopy)
                    {
                        Queries.AccessibilityQuery aq = new Queries.AccessibilityQuery(this);
                        aq.Goal = os.Alpha;
                        aq.Actions = os.ActionList;
                        aq.Pi = n.State.stateToExpression();
                        if (!aq.Excecute())
                        {
                            initialCandidates.Remove(n);
                            //throw new ParserException("Statement \"" + os.ToString() + "\" not possible!");
                        }
                    }
                }
                if (initialCandidates.Count == 0)
                {
                    throw new ArgumentException("Observable statements are imposible!");
                }
                //Czy allways jest prawidlowe
                initialCandidatesCopy = new List<Node>(initialCandidates);
                foreach (AlwaysStatement ast in _world.Statements.Always)
                {
                    foreach (Node n in initialCandidatesCopy)
                    {
                        Queries.NecessaryValueQuery ngq = new Queries.NecessaryValueQuery(this);
                        ngq.Alpha = ast.Alpha;
                        ngq.Actions = ast.ActionList;
                        ngq.Pi = n.State.stateToExpression();
                        if (!ngq.Excecute())
                        {
                            initialCandidates.Remove(n);
                            //throw new ParserException("Statement \"" + ast.ToString() + "\" not possible!");
                        }
                    }
                }
                if (initialCandidates.Count == 0)
                {
                    throw new ArgumentException("Always statements are imposible!");
                }
                foreach (Node n in initialCandidates)
                {
                    n.IsInitial = true;
                }
            }
        }

        public override string ToString()
        {
            String result = "";
            foreach (Node n in Nodes)
            {
                result += n.ToString()+Environment.NewLine;
            }
            return result;
        }
    }
}
